package com.obd.commands.io;

import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.text.TextUtils;

import com.chezai.client.bean.ObdConnState;
import com.chezai.client.global.TerminalApplication;
import com.chezai.client.utils.PreferenceManager;
import com.chezai.client.utils.ToastUtil;
import com.obd.commands.enums.ObdProtocols;
import com.obd.commands.protocol.EchoOffObdCommand;
import com.obd.commands.protocol.LineFeedOffObdCommand;
import com.obd.commands.protocol.ObdResetCommand;
import com.obd.commands.protocol.SelectProtocolObdCommand;
import com.obd.commands.protocol.TimeoutObdCommand;
import com.orhanobut.logger.Logger;

import org.greenrobot.eventbus.EventBus;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.chezai.client.bean.ObdConnState.CONNECT_INIT;
import static com.chezai.client.bean.ObdConnState.CONNECT_NO_DEVICE;


/**
 * This service is primarily responsible for establishing and maintaining a
 * permanent connection between the device where the application runs and a more
 * OBD Bluetooth interface.
 * <p/>
 * Secondarily, it will serve as a repository of ObdCommandJobs and at the same
 * time the application state-machine.
 */
public class ObdGatewayService extends Service {

    List<String> commandslist = new ArrayList<String>();
    private static final String[] PIDS = {
            "01", "02", "03", "04", "05", "06", "07", "08",
            "09", "0A", "0B", "0C", "0D", "0E", "0F", "10",
            "11", "12", "13", "14", "15", "16", "17", "18",
            "19", "1A", "1B", "1C", "1D", "1E", "1F", "20"};
    String VOLTAGE = "ATRV",
            PROTOCOL = "ATDP",
            RESET = "ATZ",
            ENGINE_COOLANT_TEMP = "0105",  //A-40
            ENGINE_RPM = "010C",  //((A*256)+B)/4
            ENGINE_LOAD = "0104",  // A*100/255
            VEHICLE_SPEED = "010D",  //A
            INTAKE_AIR_TEMP = "010F",  //A-40
            MAF_AIR_FLOW = "0110", //MAF air flow rate 0 - 655.35	grams/sec ((256*A)+B) / 100  [g/s]
            ENGINE_OIL_TEMP = "015C",  //A-40
            FUEL_RAIL_PRESSURE = "0122", // ((A*256)+B)*0.079
            INTAKE_MAN_PRESSURE = "010B", //Intake manifold absolute pressure 0 - 255 kPa
            CONT_MODULE_VOLT = "0142",  //((A*256)+B)/1000
            AMBIENT_AIR_TEMP = "0146",  //A-40
            CATALYST_TEMP_B1S1 = "013C",  //(((A*256)+B)/10)-40
            STATUS_DTC = "0101", //Status since DTC Cleared
            THROTTLE_POSITION = "0111", //Throttle position 0 -100 % A*100/255
            OBD_STANDARDS = "011C", //OBD standards this vehicle
            PIDS_SUPPORTED = "0120"; //PIDs supported

    public static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
    private static final String TAG = "ObdGatewayService";
    private final Binder _binder = new LocalBinder();
    private IPostListener _callback = null;
    private AtomicBoolean _isRunning = new AtomicBoolean(false);
    private BlockingQueue<ObdCommandJob> _queue = new LinkedBlockingQueue<>();
    private AtomicBoolean _isQueueRunning = new AtomicBoolean(false);
    private Long _queueCounter = 0L;
    private BluetoothDevice _dev = null;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case ObdConnState.CONNECT_INIT:
                    ToastUtil.showToast(getApplicationContext(), "正在连接OBD设备");
                    break;
                case ObdConnState.CONNECT_FAIL:
                    ToastUtil.showToast(getApplicationContext(), "连接车辆失败");
                    break;
                case ObdConnState.CONNECT_SUCCESS:
                    ToastUtil.showToast(getApplicationContext(), "连接车辆成功");
                    break;
                case ObdConnState.CONNECT_NO_DEVICE:
                    ToastUtil.showToast(getApplicationContext(), "没有匹配obd设备");
                    break;
            }
//            if (EventBus.getDefault().isRegistered(LearnCarFragment.class)) {
//                EventBus.getDefault().postSticky(new ObdConnState(msg.what));
//            }
            EventBus.getDefault().postSticky(new ObdConnState(msg.what));        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return _binder;
    }

    @Override
    public void onCreate() {

    }

    @Override
    public void onDestroy() {
        Logger.t(TAG).d("onDestroy: ");
        stopService();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Logger.t(TAG).d("Received start id " + startId + ": " + intent);
        startService();
        return START_STICKY;
    }

    private void startService() {

        new Thread(new Runnable() {
            @Override
            public void run() {
//                SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
                String remoteDevice = PreferenceManager.getString("bluetooth_list_preference", null);
                if (TextUtils.isEmpty(remoteDevice)) {
                    handler.sendEmptyMessage(CONNECT_NO_DEVICE);
                    stopService();
                    return;
                }
                final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
                _dev = btAdapter.getRemoteDevice(remoteDevice);
                btAdapter.cancelDiscovery();
                startObdConnection(handler);
            }
        }).start();


    }

    /**
     * Start and configure the connection to the OBD interface.
     *
     * @throws IOException
     */
    private void startObdConnection(Handler handler) {

        // Instantiate a BluetoothSocket for the remote device and connect it.
        try {
            Logger.t(TAG).d("startObdConnection");
            Message msg = new Message();
            msg.what = CONNECT_INIT;
            handler.sendMessage(msg);

            TerminalApplication._sock = _dev.createRfcommSocketToServiceRecord(MY_UUID);
            TerminalApplication._sock.connect();
            //连接成功
            Message msg2 = new Message();
            msg2.what = ObdConnState.CONNECT_SUCCESS;
            handler.sendMessage(msg2);

        } catch (IOException e) {
            Logger.t(TAG).e("连接异常：" + e.getMessage());
            Message msg = new Message();
            msg.what = ObdConnState.CONNECT_FAIL;
            handler.sendMessage(msg);
            if (handler == null) {
                return;
            }
        }

//        queueJob(new ObdCommandJob(new ObdResetCommand()));
//        queueJob(new ObdCommandJob(new EchoOffObdCommand()));
//        queueJob(new ObdCommandJob(new EchoOffObdCommand()));
//        queueJob(new ObdCommandJob(new LineFeedOffObdCommand()));
//        queueJob(new ObdCommandJob(new TimeoutObdCommand(62)));
//        // For now set protocol to AUTO
//        queueJob(new ObdCommandJob(new SelectProtocolObdCommand(ObdProtocols.AUTO)));
//        Logger.t(TAG).d("Initialization jobs queued.");

        commandslist.add(0, VOLTAGE);
        commandslist.add(1, ENGINE_RPM);
        commandslist.add(2, VEHICLE_SPEED);


        _isRunning.set(true);
        _queueCounter = 0L;
    }

    /**
     * Runs the queue until the service is stopped
     */

    private void _executeQueue() {
        Logger.t(TAG).d("Executing queue..");
        _isQueueRunning.set(true);
        while (!_queue.isEmpty()) {
            ObdCommandJob job = null;
            try {
                job = _queue.take();
                // log job
                Logger.t(TAG).d("Taking job[" + job.getId() + "] from queue..");

                if (job.getState().equals(ObdCommandJob.ObdCommandJobState.NEW)) {
                    Logger.t(TAG).d("Job state is NEW. Run it..");
                    job.setState(ObdCommandJob.ObdCommandJobState.RUNNING);
                    job.getCommand().run(TerminalApplication._sock.getInputStream(), TerminalApplication._sock.getOutputStream());
                } else {
                    // log not new job
                    Logger.t(TAG).d("Job state was not new, so it shouldn't be in queue. BUG ALERT!");
                }
            } catch (Exception e) {
                job.setState(ObdCommandJob.ObdCommandJobState.EXECUTION_ERROR);
                Logger.t(TAG).e("Failed to run command. -> " + e.getMessage());
            }
            if (job != null) {
                Logger.t(TAG).d("Job is finished.");
                job.setState(ObdCommandJob.ObdCommandJobState.FINISHED);
                if (null != _callback) {
                    _callback.stateUpdate(job);
                }

            }
        }

        _isQueueRunning.set(false);
    }

    /**
     * This method will add a job to the queue while setting its ID to the
     * internal queue counter.
     *
     * @param job
     * @return
     */
    public Long queueJob(ObdCommandJob job) {
        _queueCounter++;
        Logger.t(TAG).d("Adding job[" + _queueCounter + "] to queue..");

        job.setId(_queueCounter);
        try {
            _queue.put(job);
        } catch (InterruptedException e) {
            job.setState(ObdCommandJob.ObdCommandJobState.QUEUE_ERROR);
            // log error
            Logger.t(TAG).e("Failed to queue job.");
        }

        Logger.t(TAG).d("Job queued successfully.");
        return _queueCounter;
    }

    /**
     * Stop OBD connection and queue processing.
     */
    public void stopService() {
        Logger.t(TAG).d("Stopping service..");
        _queue.removeAll(_queue);
        _isQueueRunning.set(false);
        _callback = null;
        _isRunning.set(false);

        // close socket
        TerminalApplication.getTerminalContext().closeBluetoothSocket();
        // kill service
        stopSelf();
    }


    public class LocalBinder extends Binder implements IPostMonitor {
        public void setListener(IPostListener callback) {
            _callback = callback;
        }

        public boolean isRunning() {
            return _isRunning.get();
        }

        public void executeQueue() {
            _executeQueue();
        }

        public void addJobToQueue(ObdCommandJob job) {
            Logger.t(TAG).d("Adding job [" + job.getCommand().getName() + "] to queue.");
            _queue.add(job);

            if (!_isQueueRunning.get())
                _executeQueue();
        }
    }

}